package net.halflite.mzkr.service;

import static net.halflite.mzkr.entity.names.RegistedUserNames.address;
import static net.halflite.mzkr.entity.names.RegistedUserNames.id;
import static net.halflite.mzkr.entity.names.RegistedUserNames.modified;
import static net.halflite.mzkr.entity.names.RegistedUserNames.password;
import static net.halflite.mzkr.entity.names.RegistedUserNames.status;
import static net.halflite.mzkr.entity.names.RegistedUserNames.userImages;
import static net.halflite.mzkr.entity.names.RegistedUserNames.userProfiles;
import static net.halflite.mzkr.entity.names.RegistedUserNames.username;
import static net.halflite.mzkr.entity.names.TempUserNames.hash;
import static net.halflite.mzkr.entity.names.TempUserNames.registedUser;
import static net.halflite.mzkr.entity.names.TempUserNames.type;

import java.util.Date;

import javax.annotation.Resource;

import net.halflite.mzkr.dto.mail.UserMailDto;
import net.halflite.mzkr.dto.user.UserDto;
import net.halflite.mzkr.entity.RegistedUser;
import net.halflite.mzkr.entity.TempUser;
import net.halflite.mzkr.entity.UserProfile;
import net.halflite.mzkr.form.LoginForm;
import net.halflite.mzkr.form.home.ProfileForm;
import net.halflite.mzkr.form.regist.RegistUserForm;
import net.halflite.mzkr.form.regist.ReissuePasswordForm;
import net.halflite.mzkr.mai.UserMailMai;
import net.halflite.mzkr.type.TempUserType;
import net.halflite.mzkr.type.UserStatusType;
import net.halflite.mzkr.util.EncodeUtils;

import org.apache.commons.lang.StringUtils;
import org.apache.struts.action.ActionMessage;
import org.apache.struts.action.ActionMessages;
import org.seasar.extension.jdbc.Where;
import org.seasar.extension.jdbc.name.PropertyName;
import org.seasar.extension.jdbc.where.SimpleWhere;
import org.seasar.framework.beans.util.Beans;

/** ユーザーの登録/修正を行うビジネスロジック */
public class RegistedUserService extends AbstractService<RegistedUser> {

	@Resource
	private UserMailMai userMailMai;

	//------------------------------------------------------------ [一時登録]

	/** 一時登録情報検証 */
	public ActionMessages validateRegistTemp(RegistUserForm form) {
		ActionMessages result = new ActionMessages();
		addExistErrors(result, username(), form.username, true, "ユーザーID");
		addExistErrors(result, address(), form.address, false, "メールアドレス");
		return result;
	}
	
	/** 一時登録 */
	public void registTemp(RegistUserForm form) {
		String address = form.address;
		TempUserType userType = TempUserType.USER;
		TempUser temp = selectTempUser(address, TempConditionType.ADDRESS, userType);
		RegistedUser user = (temp == null) ? createUser(form) : updateUser(form, temp.registedUser);
		temp = (temp == null) ? new TempUser(user.id, address, userType) : setTempUser(temp, address);
		registTempUser(temp, user);
	}

	//------------------------------------------------------------ [ハッシュキー検証]

	/** ハッシュキー検証 */
	public ActionMessages validateHash(String hash, TempUserType userType) {
		ActionMessages result = new ActionMessages();
		TempUser temp = selectTempUser(hash, TempConditionType.HASH, userType);
		if (temp == null) result.add("hash", new ActionMessage("errors.invalid.hash"));
		return result;
	}
	
	//------------------------------------------------------------ [本登録]

	/** 本登録 */
	public void registUser(String hash) {
		TempUser temp = selectTempUser(hash, TempConditionType.HASH, TempUserType.USER);
		if (temp == null) return;
		RegistedUser user = temp.registedUser;
		user.status = UserStatusType.ACTIVATED;
		user.modified = new Date();
		updateIncludes(user, status(), modified());

		deleteRecord(temp);

		// 本登録メール送信
		UserMailDto dto = new UserMailDto(user.address, user.username, null);
		userMailMai.sendRegistUser(dto);
	}

	//------------------------------------------------------------ [ログイン]

	/** ログイン検証 */
	public ActionMessages validateLogin(LoginForm form) {
		ActionMessages result = new ActionMessages();
		RegistedUser user = findUser(form);
		if (user == null) {
			result.add("username", new ActionMessage("errors.invalid.login.password"));
		} else if (!user.isLoginable()) {
			result.add("username", new ActionMessage("errors.invalid.login.status"));
		}
		return result;
	}

	/** ログイン */
	public UserDto login(LoginForm form) {
		RegistedUser user = findUser(form);
		return convertUser(user);
	}

	//------------------------------------------------------------ [パスワード変更]

	/** 現在のパスワード検証 */
	public ActionMessages validateRecentPassword(long userId, String recent) {
		ActionMessages result = new ActionMessages();
		RegistedUser user = select()
			.where(new SimpleWhere()
				.eq(id(), userId)
				.eq(password(), EncodeUtils.md5(recent)))
			.getSingleResult();
		if (user == null) result.add("recent", new ActionMessage("errors.invalid", "現在のパスワード"));
		return result;
	}

	/** パスワード変更 */
	public void changePassword(long userId, String password) {
		updatePassword(userId, password);
	}
	
	//------------------------------------------------------------ [パスワード再発行]

	/** メールアドレス検証 */
	public ActionMessages validateReissuePasswordAddress(String address) {
		ActionMessages result = new ActionMessages();
		RegistedUser user = findUserByAddress(address);
		if (user == null || !user.isLoginable()) {
			result.add("address", new ActionMessage("errors.not.registed", "入力したメールアドレス"));
		}
		return result;
	}

	/** パスワード再発行メール送信 */
	public void reissuePasswordMail(String address) {
		RegistedUser user = findUserByAddress(address);
		if (user == null || !user.isLoginable()) return;
		
		TempUserType userType = TempUserType.PASSWORD;
		TempUser temp = selectTempUser(address, TempConditionType.ADDRESS, userType);
		temp = (temp == null) ? new TempUser(user.id, address, userType) : setTempUser(temp, address);
		registTempUser(temp, user);
	}

	/** パスワード変更 */
	public void reissuePassword(ReissuePasswordForm form) {
		TempUser temp = selectTempUser(form.hash, TempConditionType.HASH, TempUserType.PASSWORD);
		if (temp == null) return;
		
		RegistedUser user = temp.registedUser;
		updatePassword(user.id, form.password);
		
		deleteRecord(temp);
		
		UserMailDto dto = new UserMailDto(user.address, user.username, null);
		userMailMai.sendCompletePassword(dto);
	}

	
	//------------------------------------------------------------ [プロフィール編集]
	
	public void setProfile(ProfileForm form, long userId) {
		RegistedUser user = findUserByUserId(userId);
		UserProfile profile = user.userProfiles.isEmpty() ? new UserProfile(user.id) : user.userProfiles.get(0);
		Beans.copy(profile, form).execute();
		form.address = user.address;
	}
	
	/** メールアドレス検証 */
	public ActionMessages validateChangeAddress(String address, long userId) {
		ActionMessages result = new ActionMessages();
		if (exist(new SimpleWhere().ne(id(), userId).eq(address(), address))) {
			result.add("address", new ActionMessage("errors.registed", "入力したメールアドレス"));
		}
		return result;
	}

	public void postProfile(ProfileForm form, long userId) {
		RegistedUser user = findUserByUserId(userId);
		UserProfile profile = user.userProfiles.isEmpty() ? new UserProfile(user.id) : user.userProfiles.get(0);
		Beans.copy(form, profile).execute();
		if (profile.id == 0) {
			insertRecord(profile);
		} else {
			profile.modified = new Date();
			updateRecord(profile);
		}
		
		String address = form.address;
		if (user.address.equals(address)) return;

		// メールアドレス変更時に確認メールを送る
		TempUserType userType = TempUserType.MAIL;
		TempUser temp = selectTempUser(address, TempConditionType.ADDRESS, userType);
		temp = (temp == null) ? new TempUser(userId, address, userType) : setTempUser(temp, address);
		registTempUser(temp, user);
	}

	//------------------------------------------------------------ [メールアドレス変更]

	/** メールアドレス変更 */
	public void registMail(String hash) {
		TempUser temp = selectTempUser(hash, TempConditionType.HASH, TempUserType.MAIL);
		if (temp == null) return;
		RegistedUser user = temp.registedUser;
		user.address = temp.address;
		user.modified = new Date();
		updateIncludes(user, address(), modified());

		deleteRecord(temp);

		// メールアドレス変更完了送信
		UserMailDto dto = new UserMailDto(user.address, user.username, null);
		userMailMai.sendCompleteMail(dto);
	}

	//------------------------------------------------------------ [内部メソッド]

	private void addExistErrors(ActionMessages messages, PropertyName<String> fieldName, String value, 
			boolean ignoreStatus, String label) {
		SimpleWhere condition = new SimpleWhere().eq(fieldName, value);
		if (!ignoreStatus) condition = condition.ne(status(), UserStatusType.REGISTED);
		if (!exist(condition)) return;
		messages.add("username", new ActionMessage("errors.registed", label));
	}
	
	private RegistedUser createUser(RegistUserForm form) {
		RegistedUser user = new RegistedUser(form.username, form.address);
		user.password = EncodeUtils.md5(form.password);
		insertExcludes(user, status());
		return user;
	}
	
	private RegistedUser updateUser(RegistUserForm form, RegistedUser user) {
		user.username = form.username;
		user.password = EncodeUtils.md5(form.password);
		user.status = UserStatusType.REGISTED;
		user.modified = new Date();
		update(user);
		return user;
	}
	
	private TempUser setTempUser(TempUser temp, String address) {
		temp.address = address;
		temp.hash = EncodeUtils.createHash(address);
		return temp;
	}
	
	private void registTempUser(TempUser temp, RegistedUser user) {
		if (temp.id == 0) {
			insertRecord(temp);
		} else {
			temp.modified = new Date();
			updateRecord(temp);
		}
		
		UserMailDto dto = new UserMailDto(temp.address, user.username, temp.hash);
		temp.type.sendMail(userMailMai, dto);
	}
	
	private TempUser selectTempUser(String value, TempConditionType conditionType, TempUserType userType) {
		return select(TempUser.class)
			.innerJoin(registedUser())
			.where(conditionType.getCcndition(value, null))
			.getSingleResult();
	}
	
	private RegistedUser findUser(LoginForm form) {
		return select()
			.leftOuterJoin(userProfiles())
			.leftOuterJoin(userImages())
			.where(new SimpleWhere()
				.eq(username(), form.username)
				.eq(password(), EncodeUtils.md5(form.password)))
			.getSingleResult();
	}
	
	private UserDto convertUser(RegistedUser user) {
		UserDto dto = Beans.createAndCopy(UserDto.class, user).execute();
		dto.shortProfile = (user.userProfiles != null && !user.userProfiles.isEmpty()) ? 
				user.userProfiles.get(0).shortProfile : StringUtils.EMPTY;
		dto.userImageId = (user.userImages != null && !user.userImages.isEmpty()) ? 
				user.userImages.get(0).id : 0;
		return dto;
	}
	
	private RegistedUser findUserByAddress(String address) {
		return select()
			.where(new SimpleWhere().eq(address(), address))
			.getSingleResult();
	}
	
	private RegistedUser findUserByUserId(long userId) {
		return select()
			.leftOuterJoin(userProfiles())
			.where(new SimpleWhere().eq(id(), userId))
			.getSingleResult();
	}

	private void updatePassword(long userId, String password) {
		RegistedUser user = select()
			.where(new SimpleWhere().eq(id(), userId))
			.getSingleResult();
		user.password = EncodeUtils.md5(password);
		user.modified = new Date();
		updateIncludes(user, password(), modified());
	}

	/** 一時ユーザー情報の検索条件を表す列挙型 */
	private static enum TempConditionType {
		/** メールアドレス */
		ADDRESS {
			@Override
			public Where getCcndition(String value, TempUserType userType) {
				return createBase(userType).eq(registedUser().address(), value);
			}
		},
		/** ハッシュキー */
		HASH {
			@Override
			public Where getCcndition(String value, TempUserType userType) {
				return createBase(userType).eq(hash(), value);
			}
		};
		
		public abstract Where getCcndition(String value, TempUserType userType);
		
		private static SimpleWhere createBase(TempUserType userType) {
			return new SimpleWhere().eq(type(), userType);
		}
	}
}
